解决TableView重用机制带来的数据混乱问题

问题描述

在开发一个项目中,本地用 TableView 进行服务器的数据展示.但是最后发现,数据每隔几条就重复.通过
charles 抓取服务器端返回的数据.没有任何问题.也就是说是本地的处理方式出了问题.

问题定位

本地的 UI 逻辑比较简单.所以可以很轻松的断定是 UITableView 的重用机制导致了这个问题.以下是核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

static NSString *reuseid = @"contentCellReuse";
GQContentListCell * contentCell = [tableView dequeueReusableCellWithIdentifier:reuseid];
if (contentCell==nil) {
contentCell = [[GQContentListCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseid];
GQContentFrameModel *frameModel = [[GQContentFrameModel alloc]init];
GQContentModel *model = self.contentModelArray[indexPath.row];
frameModel.contentModel = model;
contentCell.cellFrame = frameModel;
contentCell.delegate = self;
NSDictionary *dealDict = @{@"indexPath":indexPath, @"contentModel":model};
contentCell.dealButton.myInfo = dealDict;
NSDictionary *ignoreDict= @{@"indexPath":indexPath,@"contentModel":model};

contentCell.ignoreButton.myInfo = ignoreDict;
contentCell.dealButton.myInfo = dealDict;

[contentCell.dealButton addTarget:self action:@selector(clickDealButton:) forControlEvents:UIControlEventTouchUpInside];
[contentCell.ignoreButton addTarget:self action:@selector(clickIgnoreButton:) forControlEvents:UIControlEventTouchUpInside];
}

contentCell.backgroundColor = indexPath.row%2==1?[UIColor colorWithRed:0.949f green:0.953f blue:0.961f alpha:1.00f]:[UIColor whiteColor];

return contentCell;
}

问题解决

废掉重用机制的尝试

一开始,也是最容易想到的办法,就是直接不用重用机制了.也就是将下面的代码:

1
GQContentListCell * contentCell = [tableView dequeueReusableCellWithIdentifier:reuseid];

改成:

1
GQContentListCell * contentCell = nil;

好了,问题解决了.

但是用脚趾头想想也知道,这样是不行的.因为一旦数据多起来,不卡才怪!

最终方案

静下来想,苹果做一件事情,一定不会这么弱智.所以问题肯定还是出在自己身上.
最终将 if 里面的代码拿出来,问题解决:
最终代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

static NSString *reuseid = @"contentCellReuse";
GQContentListCell * contentCell = [tableView dequeueReusableCellWithIdentifier:reuseid];
if (contentCell==nil) {
contentCell = [[GQContentListCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseid];
}

GQContentFrameModel *frameModel = [[GQContentFrameModel alloc]init];
GQContentModel *model = self.contentModelArray[indexPath.row];
frameModel.contentModel = model;
contentCell.cellFrame = frameModel;
contentCell.delegate = self;

NSDictionary *dealDict = @{@"indexPath":indexPath, @"contentModel":model};
contentCell.dealButton.myInfo = dealDict;
NSDictionary *ignoreDict= @{@"indexPath":indexPath,@"contentModel":model};

contentCell.ignoreButton.myInfo = ignoreDict;
contentCell.dealButton.myInfo = dealDict;

[contentCell.dealButton addTarget:self action:@selector(clickDealButton:) forControlEvents:UIControlEventTouchUpInside];
[contentCell.ignoreButton addTarget:self action:@selector(clickIgnoreButton:) forControlEvents:UIControlEventTouchUpInside];

contentCell.backgroundColor = indexPath.row%2==1?[UIColor colorWithRed:0.949f green:0.953f blue:0.961f alpha:1.00f]:[UIColor whiteColor];

return contentCell;
}

方案说明

假设一屏幕能显示 n 个 cell ,那么可以认为能被实例化的 cell 个数是 n+1 (或者其他,但是不会偏差太多).

那么从 n+2 个开始,就会复用前面的的 cell ,也就是说 cell==nil 这个条件就不成立了,所以 if 里面的代码就不会执行. 所以,在里面的给模型赋值的代码也就失效了. tableView 显示的数据永远就是那 n+1 个.

结论

重用 cell 的时候, 所有给数据重新赋值的操作都应该拿到 if 外面执行